資料集標註
1.1 說明:使用LabelImg框選出圖片中物件Bounding box,並標註其類別。
1.2 範例

轉換資料集標註格式
2.1 說明:資料集標註格式可區分為:PascalVOC(xml檔)、YOLO(txt檔)、COCO格式(json檔)。
2.2 範例
PascalVOC(xml檔)

YOLO(txt檔)

COCO格式(json檔)

分配訓練集(train)、驗證集(val)與測試集(test)
模型訓練
4.1 說明:讀取相對應的資料集與標籤,執行模型訓練。如:WongKinYiu/PyTorch_YOLOv4可讀取YOLO(txt檔)。
4.2 範例

LabelImg
1.1 下載LabelImg

1.2 解壓縮windows_v1.8.1。

1.3 執行LabelImg(以【第3天】資料前處理-YOLOv4與自動框選中文字示範)
執行LabelImg後,分別點選Open Dir與Change Save Dir,設定訓練集圖檔路徑及標記Annotations(xml檔)的儲存路徑。



勾選選單中View的Auto Save
框選圖片中物件(Bounding box),並標記其類別名稱 (快捷鍵:W拉框框、D下一張、A上一張)
每標記完一張圖檔 都會產出一個對應的xml檔案
流程與Python函式
1.1 PascalVOC與YOLO格式轉換公式
def convert(size, box):
    dw = 1./(size[0])
    dh = 1./(size[1])
    x = (box[0] + box[1])/2.0 - 1
    y = (box[2] + box[3])/2.0 - 1
    w = box[1] - box[0]
    h = box[3] - box[2]
    x = x*dw
    w = w*dw
    y = y*dh
    h = h*dh
    return (x, y, w, h)
1.2 讀取PascalVOC(xml),轉換後儲存成YOLO(txt)
def convert_xml_to_voc(xmlPath):
    for xml in xmlPath:
        tree = ET.parse(xml)
        root = tree.getroot()
        filename = xml.replace('.xml', '.txt').replace('xmls', 'images')
        # 處理每個標註的Bounding box
        with open(filename, "a") as bbox:
            size = root.find('size')
            w = int(size.find('width').text)
            h = int(size.find('height').text)
            for obj in root.iter('object'):
                difficult = obj.find('difficult').text
                cls = obj.find('name').text
                if cls not in classes or int(difficult) == 1:
                    continue
                cls_id = classes.index(cls)
                xmlbox = obj.find('bndbox')
                b = (float(xmlbox.find('xmin').text), 
                     float(xmlbox.find('xmax').text), 
                     float(xmlbox.find('ymin').text),
                     float(xmlbox.find('ymax').text))
                bb = convert((w, h), b)
                bbox.write(str(cls_id) + " " + 
                           " ".join([str(a) for a in bb]) + '\n')
    print('1. 將標籤從xml轉換成voc格式:完成')
    print('-'*50)
1.3 將圖片與YOLO(txt)標籤依照比例分配train與val
def train_val_split(imagePath, ratio):
    # 檔案順序隨機
    random.shuffle(imagePath)
    # 分配訓練或驗證集(依照ratio比例分配)
    pic_num = len(imagePath)
    train_num = int(pic_num * ratio)
    train_pic = imagePath[:train_num]
    train_voc = [i.replace('.png', '.txt').replace('.jpg', '.txt') 
                 for i in train_pic]
    train_list = train_pic + train_voc
    val_pic = imagePath[train_num:]
    val_voc = [i.replace('.png', '.txt').replace('.jpg', '.txt') 
               for i in val_pic]
    val_list = val_pic + val_voc
    print('2. 分配train與val資料集:完成')
    print('-'*50)
    return train_list, val_list
1.4 移動圖片與YOLO(txt)標籤到train與val資料夾
def split_images_to_train_and_val(source, train_list, val_list):
    # 創建圖片train與val資料夾
    folder1 = os.path.join(source, 'train')
    if not os.path.exists(folder1):
        os.makedirs(folder1)
    folder2 = os.path.join(source, 'val')
    if not os.path.exists(folder2):
        os.makedirs(folder2)
    # 移動圖片到資料夾
    for move_it in train_list:
        shutil.move(move_it, move_it.replace('images', 'train'))
    for move_it in val_list:
        shutil.move(move_it, move_it.replace('images', 'val'))
    print('3. 移動圖片與voc標籤到train與val資料夾:完成')
完整程式碼
import xml.etree.ElementTree as ET
import os
import random
import shutil
def convert(size, box):
    dw = 1./(size[0])
    dh = 1./(size[1])
    x = (box[0] + box[1])/2.0 - 1
    y = (box[2] + box[3])/2.0 - 1
    w = box[1] - box[0]
    h = box[3] - box[2]
    x = x*dw
    w = w*dw
    y = y*dh
    h = h*dh
    return (x, y, w, h)
def convert_xml_to_voc(xmlPath):
    for xml in xmlPath:
        tree = ET.parse(xml)
        root = tree.getroot()
        filename = xml.replace('.xml', '.txt').replace('xmls', 'images')
        # 處理每個標註的Bounding box
        with open(filename, "a") as bbox:
            size = root.find('size')
            w = int(size.find('width').text)
            h = int(size.find('height').text)
            for obj in root.iter('object'):
                difficult = obj.find('difficult').text
                cls = obj.find('name').text
                if cls not in classes or int(difficult) == 1:
                    continue
                cls_id = classes.index(cls)
                xmlbox = obj.find('bndbox')
                b = (float(xmlbox.find('xmin').text),
                     float(xmlbox.find('xmax').text),
                     float(xmlbox.find('ymin').text),
                     float(xmlbox.find('ymax').text))
                bb = convert((w, h), b)
                bbox.write(str(cls_id) + " " +
                           " ".join([str(a) for a in bb]) + '\n')
    print('1. 將標籤從xml轉換成voc格式:完成')
    print('-'*50)
# 將圖片依照比例分配train與val
def train_val_split(imagePath, ratio):
    # 檔案順序隨機
    random.shuffle(imagePath)
    # 分配訓練或驗證集(依照ratio比例分配)
    pic_num = len(imagePath)
    train_num = int(pic_num * ratio)
    train_pic = imagePath[:train_num]
    train_voc = [i.replace('.png', '.txt').replace('.jpg', '.txt')
                 for i in train_pic]
    train_list = train_pic + train_voc
    val_pic = imagePath[train_num:]
    val_voc = [i.replace('.png', '.txt').replace('.jpg', '.txt')
               for i in val_pic]
    val_list = val_pic + val_voc
    print('2. 分配train與val資料集:完成')
    print('-'*50)
    return train_list, val_list
# 移動圖片到train與val資料夾
def split_images_to_train_and_val(source, train_list, val_list):
    # 創建圖片train與val資料夾
    folder1 = os.path.join(source, 'train')
    if not os.path.exists(folder1):
        os.makedirs(folder1)
    folder2 = os.path.join(source, 'val')
    if not os.path.exists(folder2):
        os.makedirs(folder2)
    # 移動圖片到資料夾
    for move_it in train_list:
        shutil.move(move_it, move_it.replace('images', 'train'))
    for move_it in val_list:
        shutil.move(move_it, move_it.replace('images', 'val'))
    print('3. 移動圖片與voc標籤到train與val資料夾:完成')
if __name__ == '__main__':
    source = './dataests2/'
    # 讀取標籤類別
    with open(os.path.join(source, 'classes.txt'), encoding='utf-8') as f:
        classes = f.read().strip().split()
    # xml資料夾路徑
    xmlDir = os.path.join(source, 'xmls/')
    # xml檔案路徑
    xmlPath = os.listdir(xmlDir)
    xmlPath = [xmlDir + i for i in xmlPath]
    # image資料夾路徑
    imageDir = os.path.join(source, 'images/')
    # image檔案路徑
    imagePath = os.listdir(imageDir)
    imagePath = [imageDir + i for i in imagePath]
    convert_xml_to_voc(xmlPath)
    train_list, val_list = train_val_split(imagePath, 0.8)
    split_images_to_train_and_val(source, train_list, val_list)
執行程式
3.1 執行前
資料夾結構

images

xmls

classes

3.2 執行後
執行結果

資料夾結構

train

val

讓我們繼續看下去...